﻿// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
//
// SearchResultsPage.xaml.cpp
// Implementation of the SearchResultsPage class.
//

#include "pch.h"
#include <collection.h>
#include <producerconsumerstream.h>
#include "BingLocationService.h"
#include "ExceptionPolicyFactory.h"
#include "DataModel\ForecastManager.h"
#include "LocationData.h"
#include "MainPage.xaml.h"
#include "SearchResultsPage.xaml.h"

using namespace Weathr;

using namespace concurrency;
using namespace Platform;
using namespace Platform::Collections;
using namespace std;
using namespace web::http;
using namespace web::http::client;
using namespace Windows::ApplicationModel;
using namespace Windows::ApplicationModel::Activation;
using namespace Windows::Foundation;
using namespace Windows::Foundation::Collections;
using namespace Windows::UI::Xaml;
using namespace Windows::UI::Xaml::Controls;
using namespace Windows::UI::Xaml::Controls::Primitives;
using namespace Windows::UI::Xaml::Data;
using namespace Windows::UI::Xaml::Input;
using namespace Windows::UI::Xaml::Interop;
using namespace Windows::UI::Xaml::Media;
using namespace Windows::UI::Xaml::Media::Imaging;
using namespace Windows::UI::Xaml::Navigation;
using namespace Windows::Storage;

// These map country names to two-letter country codes. I use the two-letter code to download flag images from geonames.org.
// I hard-coded the mappings for simplicity. To make things more flexible, you could load these from a data file or dynamically from the web.
// From http://www.geonames.org/countries/
const map<const wstring, const wstring> Countries = {
    { L"Andorra", L"AD" },
    { L"United Arab Emirates", L"AE" },
    { L"Afghanistan", L"AF" },
    { L"Antigua and Barbuda", L"AG" },
    { L"Anguilla", L"AI" },
    { L"Albania", L"AL" },
    { L"Armenia", L"AM" },
    //{ L"", L"AN" },
    { L"Angola", L"AO" },
    { L"Antarctica", L"AQ" },
    { L"Argentina", L"AR" },
    { L"American Samoa", L"AS" },
    { L"Austria", L"AT" },
    { L"Australia", L"AU" },
    { L"Aruba", L"AW" },
    { L"Åland", L"AX" },
    { L"Azerbaijan", L"AZ" },
    { L"Bosnia and Herzegovina", L"BA" },
    { L"Barbados", L"BB" },
    { L"Bangladesh", L"BD" },
    { L"Belgium", L"BE" },
    { L"Burkina Faso", L"BF" },
    { L"Bulgaria", L"BG" },
    { L"Bahrain", L"BH" },
    { L"Burundi", L"BI" },
    { L"Benin", L"BJ" },
    { L"Saint Barthélemy", L"BL" },
    { L"Bermuda", L"BM" },
    { L"Brunei", L"BN" },
    { L"Bolivia", L"BO" },
    { L"Bonaire", L"BQ" },
    { L"Brazil", L"BR" },
    { L"Bahamas", L"BS" },
    { L"Bhutan", L"BT" },
    { L"Bouvet Island", L"BV" },
    { L"Botswana", L"BW" },
    { L"Belarus", L"BY" },
    { L"Belize", L"BZ" },
    { L"Canada", L"CA" },
    { L"Cocos Islands", L"CC" },
    { L"Keeling Islands", L"CC" },
    { L"Democratic Republic of the Congo", L"CD" },
    { L"Congo (DRC)", L"CD" },
    { L"Central African Republic", L"CF" },
    { L"Republic of the Congo", L"CG" },
    { L"Switzerland", L"CH" },
    { L"Ivory Coast", L"CI" },
    { L"Cook Islands", L"CK" },
    { L"Chile", L"CL" },
    { L"Cameroon", L"CM" },
    { L"China", L"CN" },
    { L"Colombia", L"CO" },
    { L"Costa Rica", L"CR" },
    //{ L"", L"CS" },
    { L"Cuba", L"CU" },
    { L"Cape Verde", L"CV" },
    { L"Curacao", L"CW" },
    { L"Christmas Island", L"CX" },
    { L"Cyprus", L"CY" },
    { L"Czech Republic", L"CZ" },
    { L"Germany", L"DE" },
    { L"Djibouti", L"DJ" },
    { L"Denmark", L"DK" },
    { L"Dominica", L"DM" },
    { L"Dominican Republic", L"DO" },
    { L"Algeria", L"DZ" },
    { L"Ecuador", L"EC" },
    { L"Estonia", L"EE" },
    { L"Egypt", L"EG" },
    { L"Western Sahara", L"EH" },
    { L"Eritrea", L"ER" },
    { L"Spain", L"ES" },
    { L"Ethiopia", L"ET" },
    { L"Finland", L"FI" },
    { L"Fiji", L"FJ" },
    { L"Falkland Islands", L"FK" },
    { L"Micronesia", L"FM" },
    { L"Faroe Islands", L"FO" },
    { L"France", L"FR" },
    { L"Gabon", L"GA" },
    { L"United Kingdom", L"GB" },
    { L"Grenada", L"GD" },
    { L"Georgia", L"GE" },
    { L"French Guiana", L"GF" },
    { L"Guernsey", L"GG" },
    { L"Ghana", L"GH" },
    { L"Gibraltar", L"GI" },
    { L"Greenland", L"GL" },
    { L"Gambia", L"GM" },
    { L"Guinea", L"GN" },
    { L"Guadeloupe", L"GP" },
    { L"Equatorial Guinea", L"GQ" },
    { L"Greece", L"GR" },
    { L"South Georgia and the South Sandwich Islands", L"GS" },
    { L"Guatemala", L"GT" },
    { L"Guam", L"GU" },
    { L"Guinea-Bissau", L"GW" },
    { L"Guyana", L"GY" },
    { L"Hong Kong", L"HK" },
    { L"Heard Island and McDonald Islands", L"HM" },
    { L"Honduras", L"HN" },
    { L"Croatia", L"HR" },
    { L"Haiti", L"HT" },
    { L"Hungary", L"HU" },
    { L"Indonesia", L"ID" },
    { L"Ireland", L"IE" },
    { L"Israel", L"IL" },
    { L"Isle of Man", L"IM" },
    { L"India", L"IN" },
    { L"British Indian Ocean Territory", L"IO" },
    { L"Iraq", L"IQ" },
    { L"Iran", L"IR" },
    { L"Iceland", L"IS" },
    { L"Italy", L"IT" },
    { L"Jersey", L"JE" },
    { L"Jamaica", L"JM" },
    { L"Jordan", L"JO" },
    { L"Japan", L"JP" },
    { L"Kenya", L"KE" },
    { L"Kyrgyzstan", L"KG" },
    { L"Cambodia", L"KH" },
    { L"Kiribati", L"KI" },
    { L"Comoros", L"KM" },
    { L"Saint Kitts and Nevis", L"KN" },
    { L"North Korea", L"KP" },
    { L"South Korea", L"KR" },
    { L"Kuwait", L"KW" },
    { L"Cayman Islands", L"KY" },
    { L"Kazakhstan", L"KZ" },
    { L"Laos", L"LA" },
    { L"Lebanon", L"LB" },
    { L"Saint Lucia", L"LC" },
    { L"Liechtenstein", L"LI" },
    { L"Sri Lanka", L"LK" },
    { L"Liberia", L"LR" },
    { L"Lesotho", L"LS" },
    { L"Lithuania", L"LT" },
    { L"Luxembourg", L"LU" },
    { L"Latvia", L"LV" },
    { L"Libya", L"LY" },
    { L"Morocco", L"MA" },
    { L"Monaco", L"MC" },
    { L"Moldova", L"MD" },
    { L"Montenegro", L"ME" },
    { L"Saint Martin", L"MF" },
    { L"Madagascar", L"MG" },
    { L"Marshall Islands", L"MH" },
    { L"Macedonia", L"MK" },
    { L"Mali", L"ML" },
    { L"Myanmar", L"MM" },
    { L"Burma", L"MM" },
    { L"Mongolia", L"MN" },
    { L"Macao", L"MO" },
    { L"Northern Mariana Islands", L"MP" },
    { L"Martinique", L"MQ" },
    { L"Mauritania", L"MR" },
    { L"Montserrat", L"MS" },
    { L"Malta", L"MT" },
    { L"Mauritius", L"MU" },
    { L"Maldives", L"MV" },
    { L"Malawi", L"MW" },
    { L"Mexico", L"MX" },
    { L"Malaysia", L"MY" },
    { L"Mozambique", L"MZ" },
    { L"Namibia", L"NA" },
    { L"New Caledonia", L"NC" },
    { L"Niger", L"NE" },
    { L"Norfolk Island", L"NF" },
    { L"Nigeria", L"NG" },
    { L"Nicaragua", L"NI" },
    { L"Netherlands", L"NL" },
    { L"Norway", L"NO" },
    { L"Nepal", L"NP" },
    { L"Nauru", L"NR" },
    { L"Niue", L"NU" },
    { L"New Zealand", L"NZ" },
    { L"Oman", L"OM" },
    { L"Panama", L"PA" },
    { L"Peru", L"PE" },
    { L"French Polynesia", L"PF" },
    { L"Papua New Guinea", L"PG" },
    { L"Philippines", L"PH" },
    { L"Pakistan", L"PK" },
    { L"Poland", L"PL" },
    { L"Saint Pierre and Miquelon", L"PM" },
    { L"Pitcairn Islands", L"PN" },
    { L"Puerto Rico", L"PR" },
    { L"Palestine", L"PS" },
    { L"Portugal", L"PT" },
    { L"Palau", L"PW" },
    { L"Paraguay", L"PY" },
    { L"Qatar", L"QA" },
    { L"Réunion", L"RE" },
    { L"Romania", L"RO" },
    { L"Serbia", L"RS" },
    { L"Russia", L"RU" },
    { L"Rwanda", L"RW" },
    { L"Saudi Arabia", L"SA" },
    { L"Solomon Islands", L"SB" },
    { L"Seychelles", L"SC" },
    { L"Sudan", L"SD" },
    { L"Sweden", L"SE" },
    { L"Singapore", L"SG" },
    { L"Saint Helena", L"SH" },
    { L"Slovenia", L"SI" },
    { L"Svalbard and Jan Mayen", L"SJ" },
    { L"Slovakia", L"SK" },
    { L"Sierra Leone", L"SL" },
    { L"San Marino", L"SM" },
    { L"Senegal", L"SN" },
    { L"Somalia", L"SO" },
    { L"Suriname", L"SR" },
    { L"South Sudan", L"SS" },
    { L"São Tomé and Príncipe", L"ST" },
    { L"El Salvador", L"SV" },
    { L"Sint Maarten", L"SX" },
    { L"Syria", L"SY" },
    { L"Swaziland", L"SZ" },
    { L"Turks and Caicos Islands", L"TC" },
    { L"Chad", L"TD" },
    { L"French Southern Territories", L"TF" },
    { L"Togo", L"TG" },
    { L"Thailand", L"TH" },
    { L"Tajikistan", L"TJ" },
    { L"Tokelau", L"TK" },
    { L"East Timor", L"TL" },
    { L"Turkmenistan", L"TM" },
    { L"Tunisia", L"TN" },
    { L"Tonga", L"TO" },
    { L"Turkey", L"TR" },
    { L"Trinidad and Tobago", L"TT" },
    { L"Tuvalu", L"TV" },
    { L"Taiwan", L"TW" },
    { L"Tanzania", L"TZ" },
    { L"Ukraine", L"UA" },
    { L"Uganda", L"UG" },
    { L"U.S. Minor Outlying Islands", L"UM" },
    { L"United States", L"US" },
    { L"Uruguay", L"UY" },
    { L"Uzbekistan", L"UZ" },
    { L"Vatican City", L"VA" },
    { L"Saint Vincent and the Grenadines", L"VC" },
    { L"Venezuela", L"VE" },
    { L"British Virgin Islands", L"VG" },
    { L"U.S. Virgin Islands", L"VI" },
    { L"Vietnam", L"VN" },
    { L"Vanuatu", L"VU" },
    { L"Wallis and Futuna", L"WF" },
    { L"Samoa", L"WS" },
    { L"Kosovo", L"XK" },
    { L"Yemen", L"YE" },
    { L"Mayotte", L"YT" },
    { L"South Africa", L"ZA" },
    { L"Zambia", L"ZM" },
    { L"Zimbabwe", L"ZW" },
};

// The Search Results Page item template is documented at http://go.microsoft.com/fwlink/?LinkId=234240

SearchResultsPage::SearchResultsPage() : m_defaultImagePath("ms-appx:///Assets/WeathrSquare30x30Logo.png"), m_exceptionPolicy(ExceptionPolicyFactory::GetCurrentPolicy())
{
    InitializeComponent();

    SetValue(_defaultViewModelProperty, ref new Map<String^, Object^>(std::less<String^>()));
    SetValue(_navigationHelperProperty, ref new Common::NavigationHelper(this));

    NavigationHelper->LoadState += ref new Common::LoadStateEventHandler(this, &SearchResultsPage::LoadState);
}

DependencyProperty^ SearchResultsPage::_defaultViewModelProperty =
DependencyProperty::Register("DefaultViewModel",
TypeName(IObservableMap<String^, Object^>::typeid), TypeName(SearchResultsPage::typeid), nullptr);
/// <summary>
/// used as a trivial view model.
/// </summary>
IObservableMap<String^, Object^>^ SearchResultsPage::DefaultViewModel::get()
{
    return safe_cast<IObservableMap<String^, Object^>^>(GetValue(_defaultViewModelProperty));
}

DependencyProperty^ SearchResultsPage::_navigationHelperProperty =
DependencyProperty::Register("NavigationHelper",
TypeName(Common::NavigationHelper::typeid), TypeName(SearchResultsPage::typeid), nullptr);

/// <summary>
/// Gets an implementation of <see cref="NavigationHelper"/> designed to be
/// used as a trivial view model.
/// </summary>
Common::NavigationHelper^ SearchResultsPage::NavigationHelper::get()
{
    //	return _navigationHelper;
    return safe_cast<Common::NavigationHelper^>(GetValue(_navigationHelperProperty));
}

#pragma region Navigation support

/// The methods provided in this section are simply used to allow
/// NavigationHelper to respond to the page's navigation methods.
/// 
/// Page specific logic should be placed in event handlers for the  
/// <see cref="NavigationHelper::LoadState"/>
/// and <see cref="NavigationHelper::SaveState"/>.
/// The navigation parameter is available in the LoadState method 
/// in addition to page state preserved during an earlier session.

void SearchResultsPage::OnNavigatedTo(NavigationEventArgs^ e)
{
    NavigationHelper->OnNavigatedTo(e);

    // Apply the animation that darkens the background.
    DarkenBackground->Begin();
}

void SearchResultsPage::OnNavigatedFrom(NavigationEventArgs^ e)
{
    NavigationHelper->OnNavigatedFrom(e);
}

#pragma endregion

/// <summary>
/// Populates the page with content passed during navigation.  Any saved state is also
/// provided when recreating a page from a prior session.
/// </summary>
/// <param name="navigationParameter">The parameter value passed to
/// <see cref="Frame::Navigate(Type, Object)"/> when this page was initially requested.
/// </param>
/// <param name="pageState">A map of state preserved by this page during an earlier
/// session.  This will be null the first time a page is visited.</param>
void SearchResultsPage::LoadState(Object^ sender, Common::LoadStateEventArgs^ e)
{
    (void) sender;	// Unused parameter

    // Unpack the two values passed in the parameter object: query text and previous Window content
    auto queryText = safe_cast<String^>(e->NavigationParameter);

    task_completion_event<bool> evt = Logger::LogAsyncAction(this, "Searching for '" + queryText + "'...");

    // TODO: Application-specific searching logic.  The search process is responsible for
    //       creating a list of user-selectable result categories:
    //
    //       filterList->Append(ref new SearchResultsPageFilter("<filter name>", <result count>), false);
    //
    //       Only the first filter, typically "All", should pass true as a third argument in
    //       order to start in an active state.  Results for the active filter are provided
    //       in Filter_SelectionChanged below.
    GetResultsAsync(queryText).then([this, evt](task<vector<shared_ptr<Web::Location>>> previousTask)
    {
        assert(IsMainThread());
        bool failed = true;
        try
        {
            auto results = previousTask.get();
            if (results.size() > 0)
            {
                ShowResults(results);
                failed = false;
            }
        }
        catch (const task_canceled&)
        {
            // TODO: When does this ever happen?
        }
        catch (COMException^ e)
        {
            // Not connected to the Internet.
        }
        catch (const Web::WebDataException&)
        {
            // Search yielded no results.
        }
        catch (const Web::WebConnectionException&)
        {
            // Invalid API key or other connection failure.
        }

        // Display informational text when there are no search results.
        if (failed)
        {
            VisualStateManager::GoToState(this, "NoResultsFound", true);
        }
        evt.set(!failed);

    }, task_continuation_context::use_current()).then(ObserveException<void>(m_exceptionPolicy));

    auto filterList = ref new Vector<Object^>();
    filterList->Append(ref new SearchResultsPageFilter("All", 0, true));

    // Communicate results through the view model
    DefaultViewModel->Insert("QueryText", "\u201c" + queryText + "\u201d");
    DefaultViewModel->Insert("Filters", filterList);
    DefaultViewModel->Insert("ShowFilters", filterList->Size > 1);
}

/// <summary>
/// Invoked when a filter is selected using a RadioButton when not snapped.
/// </summary>
/// <param name="sender">The selected RadioButton instance.</param>
/// <param name="e">Event data describing how the RadioButton was selected.</param>
void SearchResultsPage::Filter_Checked(Object^ sender, RoutedEventArgs^ e)
{
    (void) e;	// Unused parameter
    auto filter = dynamic_cast<FrameworkElement^>(sender)->DataContext;

    // Mirror the change into the CollectionViewSource.
    // This is most likely not needed.
    if (filtersViewSource->View != nullptr)
    {
        filtersViewSource->View->MoveCurrentTo(filter);
    }

    // Determine what filter was selected
    auto selectedFilter = dynamic_cast<SearchResultsPageFilter^>(filter);
    if (selectedFilter != nullptr)
    {
        // Mirror the results into the corresponding filter object to allow the
        // RadioButton representation used when not snapped to reflect the change
        selectedFilter->Active = true;

        // TODO: Respond to the change in active filter by calling DefaultViewModel->Insert("Results", <value>)
        //       where <value> is a collection of items with bindable Image, Title, Subtitle, and Description properties

        // Ensure results are found
        IVector<Object^>^ resultsCollection;
        if (this->DefaultViewModel->HasKey("Results") == true)
        {
            resultsCollection = dynamic_cast<IVector<Object^>^>(this->DefaultViewModel->Lookup("Results"));
            if (resultsCollection != nullptr && resultsCollection->Size != 0)
            {
                VisualStateManager::GoToState(this, "ResultsFound", true);
                return;
            }
        }
    }

    // Display informational text when there are no search results.
    VisualStateManager::GoToState(this, "NoResultsFound", true);
}

// Retrieves results from the Bing Maps location service.
task<vector<shared_ptr<Web::Location>>> SearchResultsPage::GetResultsAsync(String^ queryText)
{
    m_searchCancellationToken.cancel();
    m_searchCancellationToken = cancellation_token_source();

    // Don't search for 0-length locations.
    if (queryText == nullptr || queryText->Length() == 0)
    {
        return task_from_result(vector<shared_ptr<Web::Location>>());
    }
    else
    {
        auto locService = make_shared<Web::BingLocationService>();
        return run_async_with_retry<vector<shared_ptr<Web::Location>>>([locService, queryText]()
        {
            return locService->GetLocationsAsync(queryText->Data());

        }, [](uint32_t retryCount)
        {
            // Retry up to 2 times.
            if (retryCount < 2u)
            {
                // For the case of an HTTP request, create a delay before returning.
                return create_delayed_task(std::chrono::seconds(1), []{ return true; });
            }
            else
            {
                // Otherwise, cancel processing as soon as possible.
                return task_from_result(false);
            }
        });
    }
}

void SearchResultsPage::ShowResults(const vector<shared_ptr<Web::Location>> &results)
{
    IVector<Object^>^ resultsCollection;
    if (this->DefaultViewModel->HasKey("Results") == true)
    {
        resultsCollection = dynamic_cast<IVector<Object^>^>(this->DefaultViewModel->Lookup("Results"));
    }
    else
    {
        resultsCollection = ref new Vector<Object^>();
        DefaultViewModel->Insert("Results", resultsCollection);
    }

    // Group all search results by country. We'll later download the flag image for each group.
    // Doing so in groups is not only more efficent, but also eliminates the race condition where the same 
    // image might be downloaded and written to disk muliple times concurrently.
    map<wstring, vector<SearchResult^>> searchResultGroups;

    for (auto&& result : results)
    {
        if (result == nullptr)
        {
            continue;
        }
        auto searchResult = ref new SearchResult();
        searchResult->ImagePath = m_defaultImagePath;
        searchResult->Description = ref new String(result->AdminDistrict.c_str());
        searchResult->Title = ref new String(result->Locality.c_str());
        searchResult->Subtitle = ref new String(result->CountryRegion.c_str());
        searchResult->Location = result;
        resultsCollection->Append(searchResult);

        auto it = Countries.find(result->CountryRegion);
        // Check if country code exists.
        if (it != end(Countries))
        {
            // Insert into map.
            searchResultGroups[it->second].emplace_back(searchResult);
        }
        else
        {
            Logger::LogWarning(this, "No country code exists for '" + ref new String(result->CountryRegion.c_str()) + "'");
        }
    }

    // Get flag image for each set of results grouped by country.
    for (auto&& pair : searchResultGroups)
    {
        GetFlagImageAsync(pair.second, String::Concat(ref new String(pair.first.c_str()), ".gif")).then(ObserveException<void>(m_exceptionPolicy));
    }

    auto filterList = ref new Vector<Object^>();
    filterList->Append(ref new SearchResultsPageFilter("All", resultsCollection->Size, true));
}

void SearchResultsPage::ResultsGridView_ItemClick(Object^ sender, ItemClickEventArgs^ e)
{
    auto searchResult = safe_cast<SearchResult^>(e->ClickedItem);

    LatLon latlon(searchResult->Location->Latlon);

    // Add forecast.
    auto location = searchResult->Location;
    auto forecast = ForecastManager::Current->AddForecast(latlon, ref new String(location->Locality.c_str()),
        ref new String(location->AdminDistrict.c_str()), ref new String(location->CountryRegion.c_str()));
    // Select it.
    forecast->ParentGroup->Current = forecast;

    // Save locations file to disk in the background.
    LocationData::Current().SaveUserLocationsAsync().then(ObserveException<void>(m_exceptionPolicy));

    // Navigate to the main page.
    Frame->Navigate(TypeName(MainPage::typeid), forecast);
}

task<void> SearchResultsPage::GetFlagImageAsync(const vector<SearchResult^>& searchResults, String^ fileName)
{
    assert(IsMainThread()); // final continuation must run on main thread

    auto localFolder = ApplicationData::Current->LocalFolder;
    auto fullPath = String::Concat(String::Concat(localFolder->Path, LR"(\Flags\)"), fileName);

    return create_task(localFolder->CreateFolderAsync("Flags", CreationCollisionOption::OpenIfExists)).then([this, fileName](StorageFolder^ folder)
    {
        assert(IsBackgroundThread());

        return run_async_with_retry<StorageFile^>([folder, fileName](){ return create_task(folder->GetFileAsync(fileName)); }, [this, fileName, folder](uint32_t retryCount)
        {
            // If the first try fails, it's likely because we don't yet have the file.
            if (retryCount == 0u)
            {
                return DownloadFlagImageAsync(folder, fileName);
            }
            else
            {
                // Otherwise, cancel processing as soon as possible.
                return task_from_result(false);
            }
        });

    }, task_continuation_context::use_arbitrary()).then([searchResults, fullPath](StorageFile^ file)
    {
        assert(IsMainThread());

        // Update the image paths.
        for (auto searchResult : searchResults)
        {
            searchResult->ImagePath = nullptr;
            searchResult->ImagePath = fullPath;
        }
    });
}

task<bool> SearchResultsPage::DownloadFlagImageAsync(StorageFolder^ folder, String^ fileName)
{
    // Build request URI.
    wostringstream uri;
    uri << L"http://www.geonames.org/flags/x/" << fileName->Data();

    // For this server, the URI must be lowercase.
    auto temp = uri.str();
    transform(begin(temp), end(temp), begin(temp), tolower);

    // Get the image.
    http_client client(temp);
    return client.request(methods::GET).then([this, fileName, folder](http_response response)
    {
        assert(IsBackgroundThread());

        if (response.status_code() != 200u)
        {
            // Download failed. Just use the default image.
            return task_from_result(false);
        }

        // Read the response body.
        streams::istream bodyStream = response.body();
        streams::producer_consumer_buffer<uint8_t> inBuffer;
        bodyStream.read_to_end(inBuffer).wait();
        uint8_t* ptr; size_t count;
        inBuffer.acquire(ptr, count);

        // Copy to an Array.
        auto data = ref new Array<uint8_t>(ptr, static_cast<unsigned int>(count));

        // Create the file on disk and write the data.
        return create_task(folder->CreateFileAsync(fileName, CreationCollisionOption::ReplaceExisting)).then([data](StorageFile^ file)
        {
            assert(IsBackgroundThread());
            return FileIO::WriteBytesAsync(file, data);

        }).then([]() { return true; });
    });
}

void SearchResultsPage::Image_ImageFailed(Object^ sender, ExceptionRoutedEventArgs^ e)
{
    auto searchResult = safe_cast<SearchResult^>((safe_cast<Image^>(sender))->DataContext);
    searchResult->ImagePath = m_defaultImagePath;
}

SearchResultsPageFilter::SearchResultsPageFilter(String^ name, int count, bool active) : _count(0), _active(false)
{
    Name = name;
    Count = count;
    Active = active;
}

String^ SearchResultsPageFilter::Name::get()
{
    return _name;
}

void SearchResultsPageFilter::Name::set(String^ value)
{
    if (value == _name || (value != nullptr && value->Equals(_name)))
    {
        return;
    }

    _name = value;
    OnPropertyChanged("Name");
    OnPropertyChanged("Description");
}

int SearchResultsPageFilter::Count::get()
{
    return _count;
}

void SearchResultsPageFilter::Count::set(int value)
{
    if (value == _count)
    {
        return;
    }

    _count = value;
    OnPropertyChanged("Count");
    OnPropertyChanged("Description");
}

bool SearchResultsPageFilter::Active::get()
{
    return _active;
}

void SearchResultsPageFilter::Active::set(bool value)
{
    if (value == _active)
    {
        return;
    }
    _active = value; OnPropertyChanged("Active");
}

String^ SearchResultsPageFilter::Description::get()
{
    return _name + " (" + _count.ToString() + ")";
}

void SearchResultsPageFilter::OnPropertyChanged(String^ propertyName)
{
    PropertyChanged(this, ref new PropertyChangedEventArgs(propertyName));
}